// 导入React和必要的钩子函数
import React, { useRef, forwardRef, useImperativeHandle } from "react"

// 定义子组件暴露的方法类型接口
interface ChildMethods {
  sayHello: () => void
  resetComponent: () => void
  getData: () => string
}

// 子组件Props类型定义
interface ChildProps {
  message: string
  fntoChild: (data: string) => void
}

// 子组件定义
// 使用forwardRef高阶组件包装，允许组件接收父组件传递的ref
const Child = forwardRef<ChildMethods, ChildProps>((props, ref) => {
  const { message, fntoChild } = props

  // 内部状态，对父组件不可见，但可以通过暴露的方法操作
  const [counter, setCounter] = React.useState<number>(0)
  const [internalData, setInternalData] = React.useState<string>("初始数据")

  // 使用useImperativeHandle钩子自定义暴露给父组件的实例值
  useImperativeHandle(ref, () => ({
    // 定义sayHello方法，父组件可以通过ref调用
    sayHello() {
      alert(`Hello from Child! 当前计数: ${counter}`)
      setCounter((prev) => prev + 1)
    },
    // 定义重置组件状态的方法
    resetComponent() {
      setCounter(0)
      setInternalData("初始数据")
      alert("子组件状态已重置!")
    },
    // 定义获取内部数据的方法
    getData() {
      return internalData
    },
  }))

  // 渲染子组件的UI
  return (
    <div
      style={{
        backgroundColor: "#f0f0f0",
        padding: "15px",
        borderRadius: "8px",
        margin: "10px 0",
      }}
    >
      <h3>子组件</h3>
      <p>接收到的消息: {message}</p>
      <p>内部计数: {counter}</p>
      <div>
        <input
          value={internalData}
          onChange={(e) => setInternalData(e.target.value)}
          placeholder="修改内部数据"
          style={{ marginRight: "10px" }}
        />
        <button onClick={() => fntoChild(`来自子组件的消息: ${internalData}`)}>
          调用父组件方法
        </button>
      </div>
    </div>
  )
})

// 父组件定义
function Parent() {
  // 创建一个ref对象，指定它的类型为ChildMethods
  const childRef = useRef<ChildMethods>(null)
  const [childData, setChildData] = React.useState<string>("尚未获取数据")

  // 定义按钮点击处理函数
  const handleSayHello = () => {
    // 安全地调用子组件方法，无需类型断言
    childRef.current?.sayHello()
  }

  const handleReset = () => {
    childRef.current?.resetComponent()
  }

  const handleGetData = () => {
    if (childRef.current) {
      const data = childRef.current.getData()
      setChildData(data)
    }
  }

  const fntoChild = (data: string) => {
    console.log("收到子组件消息:", data)
    alert(`父组件收到: ${data}`)
  }

  // 渲染父组件UI
  return (
    <div
      style={{
        fontFamily: "Arial",
        maxWidth: "800px",
        margin: "0 auto",
        padding: "20px",
      }}
    >
      <h1>React Refs 实战示例</h1>
      <div
        style={{
          backgroundColor: "#e9f5ff",
          padding: "20px",
          borderRadius: "8px",
          marginBottom: "20px",
        }}
      >
        <h2>父组件</h2>
        <p>
          从子组件获取的数据: <strong>{childData}</strong>
        </p>

        {/* 渲染Child组件并传递ref */}
        <Child
          ref={childRef}
          message="这是父组件传递的消息"
          fntoChild={fntoChild}
        />

        <div style={{ marginTop: "20px" }}>
          <button
            onClick={handleSayHello}
            style={{ marginRight: "10px", padding: "8px 16px" }}
          >
            调用子组件 sayHello 方法
          </button>
          <button
            onClick={handleReset}
            style={{ marginRight: "10px", padding: "8px 16px" }}
          >
            重置子组件状态
          </button>
          <button onClick={handleGetData} style={{ padding: "8px 16px" }}>
            获取子组件数据
          </button>
        </div>
      </div>

      <div
        style={{
          backgroundColor: "#f9f9f9",
          padding: "20px",
          borderRadius: "8px",
        }}
      >
        <h2>面试要点</h2>
        <ol>
          <li>
            <strong>forwardRef</strong>: 用于将ref从父组件转发到子组件的高阶组件
          </li>
          <li>
            <strong>useImperativeHandle</strong>:
            自定义暴露给父组件的实例值，控制暴露的API
          </li>
          <li>
            
            <strong>TypeScript</strong>:
            使用接口定义子组件方法类型，提高代码健壮性
          </li>
          <li>
            <strong>单向数据流</strong>:
            虽然使用ref可以调用子组件方法，但应当谨慎使用，不破坏React的单向数据流原则
          </li>
          <li>
            <strong>useRef</strong>: 创建可变的ref对象，在组件渲染周期间保持不变
          </li>
          <li>
            <strong>可选链操作符</strong>: 使用<code>?.</code>
            安全地访问可能为null的ref
          </li>
        </ol>

        <h3>使用场景</h3>
        <ul>
          <li>需要直接调用子组件方法（如触发动画、表单验证、重置等）</li>
          <li>访问子组件内部DOM节点（如焦点管理、测量尺寸）</li>
          <li>需要从子组件获取内部状态，但不想通过props来传递</li>
        </ul>

        <h3>注意事项</h3>
        <ul>
          <li>过度使用refs会破坏React的声明式编程模型</li>
          <li>应当优先考虑使用props和状态管理来解决问题</li>
          <li>refs主要用于命令式操作，如DOM操作和动画</li>
        </ul>
      </div>
    </div>
  )
}

// 导出Parent组件作为默认导出
export default Parent
